﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Administration.Health;
using Microsoft.SharePoint.Utilities;
using Microsoft.Office.Server.Search.Administration;
namespace SPHealth.SharePoint.HealthRules.SP.Rules.Configuration
{
    class WarnServiceIsSinglePointOfFailure : SPHealthAnalysisRule
    {
        #region Locals
        private string results = "";
        private const string ruleTitle = "WarnServiceIsSinglePointOfFailure";
        private const string IgnoreList = "IgnoreList";
        #endregion

        #region Constructor
        /* This rules does not require a constructor */
        #endregion

        #region Fields

        // the category to display this rule under
        public override SPHealthCategory Category
        {
            get { return SPHealthCategory.Availability; }
        }

        // the type or error to flag this rule as
        public override SPHealthCheckErrorLevel ErrorLevel
        {
            get { return SPHealthCheckErrorLevel.Warning; }
        }

        // the Explanation for the rule and what is not working
        public override string Explanation
        {
            get { return string.Format("The following services are only started on a single server in the farm:\n\r\n\r{0}", results); }
        }

        // how to fix the issue
        public override string Remedy
        {
            get { return "Ensure that the services listed are started on more than one server within the farm to provide a level of service resilience in the event of a server failure."; }
        }

        // the Summary for the rule
        public override string Summary
        {
            get { return "One or more Services are only started on a single server within the farm."; }
        }

        public override SPHealthAnalysisRuleAutomaticExecutionParameters AutomaticExecutionParameters
        {
            get
            {
                SPHealthAnalysisRuleAutomaticExecutionParameters retval = new SPHealthAnalysisRuleAutomaticExecutionParameters();
                retval.Schedule = SPHealthCheckSchedule.Daily;
                retval.Scope = SPHealthCheckScope.Any;
                retval.ServiceType = typeof(SPTimerService);
                return retval;
            }
        }
        #endregion

        #region Methods
        // used to provide the configuration list item title
        public static string GetTitle()
        {
            // return the title for the rule
            return ruleTitle;
        }

        // used to provide the default configuration settings
        public static IDictionary<string, string> GetDefaultSettings()
        {
            // return the default settings for the rule
            IDictionary<string, string> settings = new Dictionary<string, string>();
            settings.Add("Comment1", "Enter a semi-colon delimited list of services");
            settings.Add("Comment2", "that should be excluded from this check");
            settings.Add(IgnoreList, "FIMSynchronizationService;"); // TODO - complete an appropriate list of exclusions
            return settings;
        }

        // this method is called whenever the rule is executed
        public override SPHealthCheckStatus Check()
        {

            if (!SPFarm.Joined)
                throw new InvalidOperationException();

            // count the number of servers in the farm
            int serverCount = 0;
            foreach (SPServer server in SPFarm.Local.Servers)
            {
                if (server.Role == SPServerRole.Application || server.Role == SPServerRole.WebFrontEnd)
                {
                    serverCount++;
                }
            }

            // we only need to run these checks in a farm that has two or more servers
            if (serverCount >= 2)
            {

                // build our list of services to ignore
                List<string> ignores = new List<string>();
                string[] parts = common.GetRuleSetting(IgnoreList, ruleTitle).Split(new char[] { ';' });
                for (int x = 0; x < parts.Length; x++)
                {
                    if (!string.IsNullOrEmpty(parts[x]))
                    {
                        try
                        {
                            ignores.Add(parts[x]);
                        }
                        catch { /* Duplicate? */ }
                    }
                }

                // reset our results
                results = "";
                StringBuilder sb = new StringBuilder();
                SortedList<string, string> singleInstances = new SortedList<string, string>();

                // get all the services
                SPServiceCollection services = SPFarm.Local.Services;
                foreach (SPService service in services)
                {
                    // we don't need to check this service if its on our ignore list
                    if (!ignores.Contains(service.Name) && !ignores.Contains(service.ToString()))
                    {
                        
                        // count the number of service instances that are online
                        int onlineServiceInstanceCount = 0;
                        string serverName = "";
                        foreach (SPServiceInstance si in service.Instances)
                        {
                            if (si.Status == SPObjectStatus.Online)
                            {
                                onlineServiceInstanceCount++;
                                serverName = si.Server.Name;
                            }
                        }
                        // there is only a single instance of this service running
                        // zero is fine and more than one if fine but only one might be an issue
                        if (onlineServiceInstanceCount == 1)
                            singleInstances.Add(string.IsNullOrEmpty(service.Name) ? service.ToString() : service.Name, string.Format("'{0}' is running only on '{1}'.", string.IsNullOrEmpty(service.Name) ? service.ToString() : service.Name, serverName));
                    }
                }

                // create the output
                foreach (string key in singleInstances.Keys)
                {
                    sb.AppendLine(singleInstances[key]);
                }
                results = sb.ToString();

                // determine is the check has passed or failed.
                if (!string.IsNullOrEmpty(results))
                    return SPHealthCheckStatus.Failed;
            }

            return SPHealthCheckStatus.Passed;
        }

        #endregion

    }
}
